package hasthi.common;

import hasthi.common.HasthiException.FaultCode;
import hasthi.manager.ManageMeRequestDocument;
import hasthi.manager.ManagerHeartBeatResponseDocument;
import hasthi.manager.ManagerHeartbeatDocument;
import hasthi.wsrf.WsrfException;

import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.xml.namespace.QName;

import org.apache.xmlbeans.SchemaType;
import org.apache.xmlbeans.XmlBeans;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.XmlCursor.TokenType;
import org.w3.x2003.x05.soapEnvelope.Body;
import org.w3.x2003.x05.soapEnvelope.EnvelopeDocument;
import org.w3.x2003.x05.soapEnvelope.Header;
import org.w3.x2005.x08.addressing.ActionDocument;
import org.w3.x2005.x08.addressing.MessageIDDocument;
import org.w3.x2005.x08.addressing.ToDocument;

import wsrf.GetResourcePropertyDocumentResponseDocument;

import static hasthi.common.constants.HasthiConstants.*;

public class XmlBeanUtils {

    /**
     * If a type is being passed to this, c.toNextToken() must be invoked before
     * that. This is to resuse the token. <code> 
     *   XmlCursor c = resourcePropertyResponse.newCursor();
     *   c.toNextToken();
     *  </code>
     * 
     * @param c
     * @param name
     * @param value
     * @throws HasthiException
     */
    public static void addNameValuePair(XmlCursor c, QName name, Object value)
            throws HasthiException {
        c.beginElement(name);

        if (value instanceof String) {
            c.insertChars((String) value);
        } else if (value instanceof XmlObject) {
            // Make sure this works, code is taken from
            // http://xmlbeans.apache.org/docs/2.0.0/guide/conHandlingAny.html
            XmlCursor cc = ((XmlObject) value).newCursor();
            cc.toFirstContentToken();
            cc.copyXml(c);
            cc.dispose();
        } else if (value instanceof Integer || value instanceof Double || value instanceof Float
                || value instanceof Long) {
            c.insertChars(String.valueOf(value));
        } else {
            throw new HasthiException("Setting unsupported value for " + name + " = " + value + " "
                    + value.getClass(), FaultCode.InvaliedLocalArgumnet);
        }
        c.toParent();
    }

    public static void addNameValuePair(XmlObject parent, QName name, Object value)
            throws HasthiException {
        XmlCursor c = parent.newCursor();
        // TODO may be we need to check we are not moving in to a namespace or a
        // attribute.
        c.toNextToken();
        XmlBeanUtils.addNameValuePair(c, name, value);
        c.dispose();
    }

//    public static void addChildrenFromSrcDest(XmlObject dest, XmlObject src) {
//        XmlCursor c = src.newCursor();
//        XmlCursor cc = dest.newCursor();
//        c.selectPath("*");
//        // iterate over the selection
//        while (c.toNextSelection()) {
//            c.copyXml(cc);
//        }
//        c.dispose();
//        cc.dispose();
//    }
    
    public static void addElementAsChild(XmlObject parent, XmlObject child) {
        try {
            XmlCursor c = parent.newCursor();
            // c.toLastChild();
            c.toEndToken();
            XmlCursor cc = child.newCursor();
            cc.toFirstContentToken();
            cc.copyXml(c);
            cc.dispose();
            c.dispose();
        } catch (IllegalArgumentException e) {
            throw new HasthiError("Error occured while trying to append " + prettyPrint(child)
                    + " to " + prettyPrint(parent));
        }
    }

    public static void addTypeAsChild(XmlObject parent, XmlObject child) {
        XmlCursor c = parent.newCursor();
        // c.toLastChild();
        c.toEndToken();
        XmlCursor cc = child.newCursor();
        cc.toFirstContentToken();
        cc.copyXml(c);
        while (cc.toNextSibling()) {
            // cc.toFirstContentToken();
            cc.copyXml(c);
        }

        cc.dispose();
        c.dispose();
    }

    public static Object readValueOffXml(XmlCursor c) throws WsrfException {
        return readValueOffXml(c, false);
    }

    /**
     * 
     * @param c
     * @param type
     *                if true return the underline XMLBeans type, otherwise
     *                return document
     * @return
     * @throws WsrfException
     */
    public static Object readValueOffXml(XmlCursor c, boolean type) {
        if (!c.toFirstChild()) {
            return c.getTextValue();
        } else {
            if (type) {
                return c.getObject();
            } else {
                c.push();
                XmlObject copy = XmlObject.Factory.newInstance();
                XmlCursor ccopy = copy.newCursor();
                ccopy.toFirstContentToken();
                c.copyXml(ccopy);
                // System.out.println("@@" +copy.xmlText());
                // ccopy.dispose();
                c.pop();
                // c.toParent();
                // XmlObject value = c.getObject();
                // c.toFirstChild();
                return copy;
            }
        }
        // Object value = null;
        // c.toNextToken();
        // //Make sure we get to a text node or a start node
        // TokenType tokenType = null;
        // while( !c.isStart() && !c.isText()){
        // tokenType = c.toNextToken();
        // if(TokenType.NONE.equals(tokenType)){
        // throw new WsrfException("Unexpected end of document");
        // }
        // }
        //        
        // if(c.isText()){
        // value = c.getTextValue();
        // }else{
        // c.toFirstChild();
        // if(c.toParent()){
        // value = c.getObject();
        // }else{
        // throw new WsrfException("Can not move back");
        // }
        // }
        // return value;
    }

    public static String getStrValue(XmlObject xmlObject) {
        XmlCursor c = xmlObject.newCursor();
        String value = c.getTextValue();
        c.dispose();
        return value;
    }

    public static QName getQNameValue(XmlObject xmlObject) {
        XmlCursor c = xmlObject.newCursor();
        String value = c.getTextValue();

        String[] splitedName = value.split(":");
        String nsURI;
        QName result;
        if (splitedName.length == 2) {
            nsURI = c.namespaceForPrefix(splitedName[0]);
            result = new QName(nsURI, splitedName[1]);
        } else {
            nsURI = c.namespaceForPrefix("");
            result = new QName(nsURI, splitedName[0]);
        }

        c.dispose();
        return result;
    }

    public static Map<QName, Object> extractProperties(XmlObject xmlObject) {
        XmlCursor c = xmlObject.newCursor();
        Map<QName, Object> properties = extractProperties(c, false);
        c.dispose();
        return properties;
    }

    public static boolean processProperties(XmlObject xmlObject, ParameterProcessor processor)
            throws HasthiException {
        XmlCursor c = xmlObject.newCursor();
        c.selectPath("*");
        // iterate over the selection
        boolean triggered = false;
        while (c.toNextSelection()) {
            // System.out.println(c.getObject());
            // two views of the same data:
            QName propertyName = c.getName();
            Object value = XmlBeanUtils.readValueOffXml(c, false);
            processor.process(propertyName, value);
            // System.out.println("@" + propertyName + " = " +
            // properties.get(propertyName));
            triggered = true;
        }
        c.dispose();
        return triggered;
    }

    /**
     * 
     * @param c
     * @param type
     *                if true return the underline XMLBeans type, otherwise
     *                return document
     * @return
     * @throws WsrfException
     */
    public static Map<QName, Object> extractProperties(XmlCursor c, boolean type) {
        Map<QName, Object> properties = new HashMap<QName, Object>();
        c.selectPath("*");
        // iterate over the selection
        while (c.toNextSelection()) {
            // System.out.println(c.getObject());
            // two views of the same data:
            QName propertyName = c.getName();
            Object value = XmlBeanUtils.readValueOffXml(c, type);
            properties.put(propertyName, value);
            // System.out.println("@" + propertyName + " = " +
            // properties.get(propertyName));
        }
        c.dispose();
        return properties;
    }

    public static String prettyPrint(XmlObject ele) {
        if (ele == null) {
            return "null";
        }
        XmlOptions options = new XmlOptions();
        options.setSavePrettyPrint().setSavePrettyPrintIndent(2);
        options.setSaveAggressiveNamespaces();
        return ele.xmlText(options);
    }

    public static XmlObject extractBodyContent(XmlObject message) throws HasthiException {
        // try {
        XmlObject bodycontent = null;
        // if(message instanceof EnvelopeDocument){
        // bodycontent =
        // XmlBeanUtils.readXmlValue(((EnvelopeDocument)message).getEnvelope().getBody());
        // }else if(message instanceof
        // org.xmlsoap.schemas.soap.envelope.EnvelopeDocument){
        // bodycontent =
        // XmlBeanUtils.readXmlValue(((org.xmlsoap.schemas.soap.envelope.EnvelopeDocument)message)
        // .getEnvelope().getBody());
        // }
        if (message instanceof EnvelopeDocument) {
            bodycontent = ((EnvelopeDocument) message).getEnvelope().getBody();
        } else if (message instanceof org.xmlsoap.schemas.soap.envelope.EnvelopeDocument) {
            bodycontent = ((org.xmlsoap.schemas.soap.envelope.EnvelopeDocument) message)
                    .getEnvelope().getBody();
        }

        if (bodycontent != null) {
            return fixType(bodycontent);
            // return bodycontent;
            // Reader staxParser = bodycontent.newReader();
            // XmlObject parsedBodyContent =
            // XmlObject.Factory.parse(staxParser);
            // staxParser.close();
            // return parsedBodyContent;
        } else {
            throw new HasthiException("Message is not a Soap Evelope but" + message,
                    FaultCode.InvaliedRequest);
        }
        // } catch (XmlException e) {
        // throw new HasthiException("Message is not a Soap Evelope",
        // FaultCode.InvaliedRequest);
        // } catch (IOException e) {
        // throw new HasthiException("Message is not a Soap Evelope",
        // FaultCode.InvaliedRequest);
        // }

        // XmlCursor c = message.newCursor();
        // c.toFirstContentToken();
        // if ("Envelope".equals(c.getName().getLocalPart())) {
        // c.toFirstChild();
        // if (!"Body".equals(c.getName().getLocalPart())) {
        // if (!c.toNextSibling()) {
        // throw new HasthiException("Message is not a Soap Evelope",
        // FaultCode.InvaliedRequest);
        // }
        // }
        // return c.getObject();
        // } else {
        // throw new HasthiException("Message is not a Soap Evelope",
        // FaultCode.InvaliedRequest);
        // }
    }

    /**
     * return xml value or null if there is none
     */
    public static XmlObject readXmlValue(XmlObject input) {
        XmlCursor c = input.newCursor();
        XmlObject value = null;
        if (c.toFirstChild()) {
            value = c.getObject();
        }
        c.dispose();
        return value;
    }

    public static String findStrByXpath(String xpath, XmlObject source) {
        XmlObject[] results = source.selectPath(xpath);
        if (results != null && results.length > 0) {
            return XmlBeanUtils.getStrValue(results[0]);
        } else {
            return null;
        }
    }
    
    public static XmlObject findByXpath(QName name, XmlObject source) {
        StringBuffer query = new StringBuffer();
        query.append("declare namespace ns ='");
        query.append(name.getNamespaceURI());
        // query.append("';*/ns:").append(name.getLocalPart());
        query.append("';$this//ns:").append(name.getLocalPart());
        // System.out.println(query.toString());
        XmlObject[] results = source.selectPath(query.toString());
        if (results != null && results.length > 0) {
            return results[0];
        } else {
            return null;
        }
    }
    
    public static XmlObject[] findAllByXpath(QName name, XmlObject source) {
        StringBuffer query = new StringBuffer();
        query.append("declare namespace ns ='");
        query.append(name.getNamespaceURI());
        // query.append("';*/ns:").append(name.getLocalPart());
        query.append("';$this//ns:").append(name.getLocalPart());
        // System.out.println(query.toString());
        return source.selectPath(query.toString());
    }

    public static QName getElementName(XmlObject xmlObject) {
        XmlCursor c = xmlObject.newCursor();
        c.toFirstContentToken();
        QName name = c.getName();
        c.dispose();
        return name;
    }

    public static QName findFirstElementName(XmlObject object) {
        XmlCursor c = ((XmlObject) object).newCursor();
        // Make sure we get to a text node or a start node
        // TokenType tokenType = c.toNextToken();
        while (!c.isStart()) {
            c.toNextToken();
        }
        QName name = c.getName();
        c.dispose();
        return name;
    }

    public static boolean isMatchingType(XmlObject type, XmlObject object) {
        QName typeName = type.schemaType().getDocumentElementName();
        return typeName.equals(findFirstElementName(object));
    }

    public static EnvelopeDocument wrapBodyContent(XmlObject bodyContent) {
        EnvelopeDocument responseAsSoap = EnvelopeDocument.Factory.newInstance();
        Body body = responseAsSoap.addNewEnvelope().addNewBody();
        XmlBeanUtils.addElementAsChild(body, bodyContent);
        return responseAsSoap;
    }

    public static EnvelopeDocument wrapBodyContentWithAddressing(XmlObject bodyContent,
            String toaddress, String action) {
        EnvelopeDocument envelope = XmlBeanUtils.wrapBodyContent(bodyContent);

        Header soapheader = envelope.getEnvelope().addNewHeader();

        MessageIDDocument messageIDDocument = MessageIDDocument.Factory.newInstance();
        messageIDDocument.addNewMessageID().setStringValue(Utils.createUUID());
        XmlBeanUtils.addElementAsChild(soapheader, messageIDDocument);

        ActionDocument actionDocument = ActionDocument.Factory.newInstance();
        actionDocument.addNewAction().setStringValue(action);
        XmlBeanUtils.addElementAsChild(soapheader, actionDocument);

        ToDocument toDocument = ToDocument.Factory.newInstance();
        toDocument.addNewTo().setStringValue(toaddress);
        XmlBeanUtils.addElementAsChild(soapheader, toDocument);

        return envelope;
    }

    public static interface ParameterProcessor {
        public void process(QName name, Object value) throws HasthiException;
    }

    private static XmlObject fixType(XmlObject source) throws HasthiException {
        // try {
        // we first move from type to document element.
        // TODO check is input a type if possible
        // QName name = XmlBeanUtils.getElementName(source);
        //            
        // if(name == null){
        // XmlCursor c =source.newCursor();
        // source = (XmlObject)XmlBeanUtils.readValueOffXml(c);
        // c.dispose();
        // QName name = XmlBeanUtils.getElementName(source);
        // }
        // source = extractXmlValue(source);
        

//        if(!c.toFirstChild()) {
//            throw new HasthiException("Not start tag found while fixing the type",FaultCode.InvaliedLocalArgumnet);
//        }
//        XmlObject copy = XmlObject.Factory.newInstance();
//        XmlCursor ccopy = copy.newCursor();
//        ccopy.toFirstContentToken();
//        c.moveXml(ccopy);
//        c.dispose();
        
        //QName name = getElementName(copy);
        XmlCursor c = source.newCursor();
        QName name;
        try{
            TokenType tokenType = null;
            while(c.hasNextToken() && tokenType != TokenType.START){
                tokenType = c.toNextToken();
            }
            if(tokenType == TokenType.START){
                name = c.getName();
              source = XmlObject.Factory.newInstance();
              XmlCursor ccopy = source.newCursor();
              ccopy.toFirstContentToken();
              c.moveXml(ccopy);
              ccopy.dispose();
            }else{
                throw new HasthiException("Not start tag found while fixing the type",FaultCode.InvaliedLocalArgumnet);
            }
        }finally{
            c.dispose();
        }
        SchemaType schemaType;
        if (MNGHB_NAME.equals(name)) {
            schemaType = ManagerHeartbeatDocument.type;
        } else if (MNGHBRES_NAME.equals(name)) {
            schemaType = ManagerHeartBeatResponseDocument.type;
        } else if (MNGMEREQ_NAME.equals(name)) {
            schemaType = ManageMeRequestDocument.type;
        } else if (new QName(MANEGER_NS, "GetResourcePropertyDocumentResponse").equals(name)) {
            schemaType = GetResourcePropertyDocumentResponseDocument.type;
        } else {
            schemaType = XmlBeans.getContextTypeLoader().findDocumentType(name);
        }
        if (schemaType == null) {
            return source;
        } else {
            XmlObject changedType =  source.changeType(schemaType);
            if(source == changedType){
                throw new HasthiException("Type Change failed",FaultCode.InvaliedLocalArgumnet);
            }
            return changedType;
        }
        // } catch (WsrfException e) {
        // throw new HasthiException(e,FaultCode.LocalError);
        // }

    }

}
